package com.duoduo.common.util.interptor;


import com.duoduo.common.api.help.PropertiesInnerUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 数据聚合平台切表工具
 * 开启后，会激活一个mybatis拦截器：
 *     如果配置在fusion.properties里的表名出现在查询sql中，且结果为表不存在异常，就会拦截并200ms后重试
 *     重试依然不存在，就会把sql表名改为聚合平台约定的导出表名后查询
 * author: zhang xuan
 * time: 2021/11/29
 */
@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
})
public class FusionInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(FusionInterceptor.class);
    private static final List<String> FUSION_TABLE_LIST;
    static {
    	String tableListString = PropertiesInnerUtil.getString("fusion.properties", "table.name.list");
    	FUSION_TABLE_LIST = StringUtils.isBlank(tableListString) ? Lists.newArrayList() : Arrays.asList(tableListString.split(","));
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Pair<Boolean, String> tableExistException;
        try {
            return invocation.proceed();
        } catch (Throwable e) {
            if (CollectionUtils.isEmpty(FUSION_TABLE_LIST)) {
                //没配置聚合表，不做处理
                throw e;
            }
            tableExistException = isTableExistException(e);
            if (!tableExistException.getKey()) {
                //不是 找不到表异常，不作处理
                throw e;
            }
            //判断找不到的表 是不是聚合表，不是就按原样抛错
            boolean isMergedTableException = false;
            for (String s : FUSION_TABLE_LIST) {
                if (tableExistException.getValue().contains(s)) {
                    isMergedTableException = true;
                    break;
                }
            }
            if (!isMergedTableException) {
                throw e;
            }
        }

        //发现聚合表不存在异常
        final Object[] queryArgs = invocation.getArgs();
        final MappedStatement mappedStatement = (MappedStatement) queryArgs[0];
        final Object parameter = queryArgs[1];
        final BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        logger.info("fusion：find table doesn't exist exception: \nsql: " + sql + "\nmsg: " + tableExistException.getValue());

        //第一次聚合表不存在异常，等200ms再试
        try {
            Thread.sleep(200);
            return invocation.proceed();
        } catch (InvocationTargetException e) {
            if (!tableExistException.getKey()) {
                //判断是不是找不到表，以及系统配没配聚合表注解，不是||没配 原样抛错
                throw e;
            }
            //判断找不到的表 是不是聚合表，不是就按原样抛错
            boolean isMergedTableException = false;
            for (String s : FUSION_TABLE_LIST) {
                if (tableExistException.getValue().contains(s)) {
                    isMergedTableException = true;
                    break;
                }
            }
            if (!isMergedTableException) {
                throw e;
            }
        }
        logger.info("fusion：find table doesn't exist exception: \nsql: " + sql + "\nmsg: " + tableExistException.getValue());

        //改写表名，后缀加_export
        String changedSql = addTableNameSuffix(sql, FUSION_TABLE_LIST, "_fusion_export");
        // 重新new一个查询语句对像
        BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), changedSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
        // 把新的查询放到statement里
        MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        queryArgs[0] = newMs;
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) { }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    /**
     * 定义: 修改表名
     */
    private String addTableNameSuffix(String sql, List<String> tableList, String suffix) {
        for (String tableNameB4 : tableList) {
            String tableNameAfter = tableNameB4 + suffix;
            sql = sql.replaceAll(tableNameB4, tableNameAfter);
        }
        return sql;
    }

    /**
     * 定义: 逐层判断异常信息
     */
    private Pair<Boolean, String> isTableExistException(Throwable e) {
        while (e != null) {
            String message = e.getMessage();
            if (StringUtils.isNotBlank(message)) {
            	Pattern p = Pattern.compile("Table '(\\S+)' doesn't exist");
            	Matcher m = p.matcher(message);
            	if(m.find()) {
            		String tableName = m.group(1);
            		if(StringUtils.isNotBlank(tableName)) {
            			return Pair.of(true, tableName);
            		}
            	}
            }
            e = e.getCause();
        }
        return Pair.of(false, null);
    }
    
}
